package com.zxjbyte.yiyi.framework.redis.lock;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import com.google.common.collect.Maps;
import com.zxjbyte.yiyi.framework.common.util.ValidationUtil;
import com.zxjbyte.yiyi.framework.redis.config.RedisProperties;
import com.zxjbyte.yiyi.framework.redis.lock.enums.RLockType;
import com.zxjbyte.yiyi.framework.redis.lock.executor.FairRLockExecutor;
import com.zxjbyte.yiyi.framework.redis.lock.executor.RLockExecutor;
import com.zxjbyte.yiyi.framework.redis.lock.executor.ReentrantRLockExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;

import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * 基于redis分布式锁的模板方法
 *
 * @Author zhangxingjia
 * @Date 2023/4/14 17:18
 * @Version: 1.0
 */
@Slf4j
@SuppressWarnings("rawtypes")
public class RLockTemplate implements InitializingBean {

    private final Map<RLockType, RLockExecutor> executorMap = Maps.newLinkedHashMap();

    private final RedisProperties.RLookProperties properties;
    private final List<RLockExecutor> executors;

    public RLockTemplate(RedisProperties properties, List<RLockExecutor> executors){
        this.properties = properties.getRedisLock();
        this.executors = executors;
    }

    /**
     * 执行加锁方法函数
     * @param key
     * @param waitTimeMillis
     * @param leaseTimeMillis
     * @param type
     * @param action
     * @return
     * @param <T>
     */
    public <T> T executeWithLock(String key, Long waitTimeMillis, Long leaseTimeMillis, RLockType type, Callable<T> action){
        RLockInfo lockInfo = null;
        try {
            lockInfo = this.lock(key, waitTimeMillis, leaseTimeMillis, TimeUnit.MILLISECONDS, type);
            if(lockInfo != null){
                return action.call();
            }
        } catch (Throwable e){
            log.error(e.getMessage(), e);
        }finally {
            if(lockInfo != null){
                this.releaseLock(lockInfo);
            }
        }
        return null;
    }


    public RLockInfo lock(String key){
        return lock(key, null, null, TimeUnit.MILLISECONDS, RLockType.Reentrant);
    }

    /**
     * 加锁
     * @param key
     * @param waitTime 表示尝试获取锁的最长等待时间。如果在这个时间范围内未获取到锁，则放弃获取。
     *      推荐值：高并发场景：5-10秒
     *             业务场景简单且锁的竞争较少：1-3秒
     *
     * @param leaseTime 表示锁的自动释放时间。如果在 leaseTime 到期之前没有手动释放锁，锁会自动失效。
     *      推荐计算公式：leaseTime=业务平均执行时间×1.5+安全冗余时间
     *      推荐值：轻量级业务操作：1-5秒，例如简单的计数更新
     *             中等复杂度业务操作：10-30秒，例如下单扣库存
     *             重型业务操作：根据实际耗时计算，但一般不超过60秒
     *
     * @param type 锁类型
     * @return
     */
    public RLockInfo lock(String key, Long waitTime, Long leaseTime, TimeUnit unit, RLockType type){
        if(waitTime == null){
            waitTime = properties.getWaitTime();
        }
        waitTime = toMillis(unit, waitTime);
        if(leaseTime == null){
            leaseTime = properties.getLeaseTime();
        }
        // leaseTime = -1 表示启用Redisson的看门狗
        if(leaseTime != -1){
            leaseTime = toMillis(unit, leaseTime);
        }
        long retryInterval = properties.getRetryInterval();
        RLockExecutor lockExecutor = getExecutor(type);
        log.debug("The current lock used type: [{}], key: [{}]", type.name(), key);
        long start = System.currentTimeMillis();
        int acquireCount = 0;
        do {
            acquireCount++;
            Object lockInst = lockExecutor.acquire(key, waitTime, leaseTime);
            if (ObjectUtil.isNotNull(lockInst)) {
                return RLockInfo.builder().lockKey(key).waitTime(waitTime).leaseTime(leaseTime)
                        .lockInst(lockInst).lockExecutor(lockExecutor).acquireCount(acquireCount).build();
            }
            ThreadUtil.sleep(retryInterval, TimeUnit.MILLISECONDS);
        } while (System.currentTimeMillis() - start < waitTime);
        return null;
    }

    @SuppressWarnings("unchecked")
    public boolean releaseLock(RLockInfo lockInfo) {
        if (ObjectUtil.isNull(lockInfo)) {
            return false;
        }
        return lockInfo.getLockExecutor().release(lockInfo.getLockKey(), lockInfo.getLockInst());
    }

    private long toMillis(TimeUnit unit, long time){
        if(ObjectUtil.equals(TimeUnit.MILLISECONDS, unit)){
            return time;
        }
        return unit.toMillis(time);
    }

    private RLockExecutor getExecutor(RLockType type) {
        final RLockExecutor lockExecutor = executorMap.get(type);
        ValidationUtil.assertNotNull(lockExecutor, type.name());
        return lockExecutor;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        ValidationUtil.assertTrue(properties.getWaitTime() >= 0, "waitTime must least 0");
        ValidationUtil.assertTrue(properties.getLeaseTime() >= -1, "leaseTime must lease -1");
        ValidationUtil.assertTrue(properties.getRetryInterval() >= 0, "retryInterval must more than 0");
        ValidationUtil.assertNotEmpty(executors, "executors must have at least one");
        executors.forEach(executor -> {
            RLockType rLockType = null;
            if(executor instanceof ReentrantRLockExecutor){
                rLockType = RLockType.Reentrant;
            } else if(executor instanceof FairRLockExecutor){
                rLockType = RLockType.Fair;
            }
            executorMap.put(rLockType, executor);
        });

    }
}
